package com.easylinkin.linkappapi.openapi.util;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Provider;
import java.security.Security;
import java.util.Map;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES加密工具类
 *
 * @author ACGkaka
 * @since 2021-06-18 19:11:03
 */
public class AESUtil {
	/**
	 * 日志相关
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(AESUtil.class);
	/**
	 * 编码
	 */
	private static final String ENCODING = "UTF-8";
	/**
	 * 算法定义
	 */
	private static final String AES_ALGORITHM = "AES";
	/**
	 * 指定填充方式
	 */
	private static final String CIPHER_PADDING = "AES/ECB/PKCS7Padding";
	private static final String CIPHER_CBC_PADDING = "AES/CBC/PKCS7Padding";

	static {
		Security.setProperty("crypto.policy", "unlimited");
		// 解决AES工具类 PKCS7报错问题
		Security.addProvider(new BouncyCastleProvider());
	}
	/**
	 * 偏移量(CBC中使用，增强加密算法强度)
	 */
	// private static final String IV_SEED = "1234567812345678";

	/**
	 * AES加密
	 * 
	 * @param content 待加密内容
	 * @param aesKey  密码
	 * @return
	 */
	public static String encrypt(String content, String aesKey) {
		if (StringUtils.isBlank(content)) {
			LOGGER.info("AES encrypt: the content is null!");
			return null;
		}
		// 判断秘钥是否为16位
		if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
			try {
				// 对密码进行编码
				byte[] bytes = aesKey.getBytes(ENCODING);
				// 设置加密算法，生成秘钥
				SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
				// "算法/模式/补码方式"
				Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
				// 选择加密
				cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
				// 根据待加密内容生成字节数组
				byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
				// 返回base64字符串
				return Base64Utils.encodeToString(encrypted);
			} catch (Exception e) {
				LOGGER.info("AES encrypt exception:" + e.getMessage());
				throw new RuntimeException(e);
			}

		} else {
			LOGGER.info("AES encrypt: the aesKey is null or error!");
			return null;
		}
	}

	/**
	 * 解密
	 * 
	 * @param content 待解密内容
	 * @param aesKey  密码
	 * @return
	 */
	public static String decrypt(String content, String aesKey) {
		if (StringUtils.isBlank(content)) {
			LOGGER.info("AES decrypt: the content is null!");
			return null;
		}
		// 判断秘钥是否为16位
		if (StringUtils.isNotBlank(aesKey) && aesKey.length() == 16) {
			try {
				// 对密码进行编码
				byte[] bytes = aesKey.getBytes(ENCODING);
				// 设置解密算法，生成秘钥
				SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
				// "算法/模式/补码方式"
				Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
				// 选择解密
				cipher.init(Cipher.DECRYPT_MODE, skeySpec);

				// 先进行Base64解码
				byte[] decodeBase64 = Base64Utils.decodeFromString(content);

				// 根据待解密内容进行解密
				byte[] decrypted = cipher.doFinal(decodeBase64);
				// 将字节数组转成字符串
				return new String(decrypted, ENCODING);
			} catch (Exception e) {
				LOGGER.info("AES decrypt exception:" + e.getMessage());
				throw new RuntimeException(e);
			}

		} else {
			LOGGER.info("AES decrypt: the aesKey is null or error!");
			return null;
		}
	}

	/**
	 * AES_CBC加密
	 * 
	 * @param content 待加密内容
	 * @param aesKey  密码
	 * @return
	 */
	public static String encryptCBC(String content, String aesKey) {
		if (StringUtils.isBlank(content)) {
			LOGGER.info("AES_CBC encrypt: the content is null!");
			return null;
		}

		// 判断秘钥是否为16位
		// aesKey.length() == 16
		if (StringUtils.isNotBlank(aesKey) && aesKey.length() <= 32) {
			try {
				// 对密码进行编码
				byte[] bytes = aesKey.getBytes(ENCODING);
				// 设置加密算法，生成秘钥
				SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
				// "算法/模式/补码方式"
				Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
				// 偏移
				IvParameterSpec iv = new IvParameterSpec(aesKey.substring(0, 16).getBytes(ENCODING));
				// 选择加密
				cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
				// 根据待加密内容生成字节数组
				byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
				// 返回base64字符串
				return Base64Utils.encodeToString(encrypted);
			} catch (Exception e) {
				LOGGER.info("AES_CBC encrypt exception:" + e.getMessage());
				throw new RuntimeException(e);
			}

		} else {
			LOGGER.info("AES_CBC encrypt: the aesKey is null or error!");
			return null;
		}
	}

	/**
	 * AES_CBC解密
	 * 
	 * @param content 待解密内容
	 * @param aesKey  密码
	 * @return
	 */
	public static String decryptCBC(String content, String aesKey) {

		if (StringUtils.isBlank(content)) {
			LOGGER.info("AES_CBC decrypt: the content is null!");
			return null;
		}

		// 判断秘钥是否为16位
		// && aesKey.length() == 16
		if (StringUtils.isNotBlank(aesKey)) {
			try {
				// 对密码进行编码
				byte[] bytes = aesKey.getBytes(ENCODING);
				// 设置解密算法，生成秘钥
				SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
				// 偏移
				IvParameterSpec iv = new IvParameterSpec(aesKey.substring(0, 16).getBytes(ENCODING));
				// "算法/模式/补码方式"
				Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);
				// 选择解密
				cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

				// 先进行Base64解码
				byte[] decodeBase64 = Base64Utils.decodeFromString(content);

				// 根据待解密内容进行解密
				byte[] decrypted = cipher.doFinal(decodeBase64);
				// 将字节数组转成字符串
				return new String(decrypted, ENCODING);
			} catch (Exception e) {
				LOGGER.info("AES_CBC decrypt exception:" + e.getMessage());
				throw new RuntimeException(e);
			}

		} else {
			LOGGER.info("AES_CBC decrypt: the aesKey is null or error!");
			return null;
		}
	}

	/**
	 * 去除jdk的128位的限制
	 */
	public static void removeCryptographyRestrictions() {

		try {
			/*
			 * Do the following, but with reflection to bypass access checks:
			 * JceSecurity.isRestricted = false; JceSecurity.defaultPolicy.perms.clear();
			 * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
			 */
			final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
			final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
			final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

			final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
			isRestrictedField.setAccessible(true);
			// 去除isRestricted的final限制
			final Field modifiersField = Field.class.getDeclaredField("modifiers");
			modifiersField.setAccessible(true);
			modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
			isRestrictedField.set(null, false);

			final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
			defaultPolicyField.setAccessible(true);
			final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

			final Field perms = cryptoPermissions.getDeclaredField("perms");
			perms.setAccessible(true);
			((Map<?, ?>) perms.get(defaultPolicy)).clear();

			final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
			instance.setAccessible(true);
			defaultPolicy.add((Permission) instance.get(null));

		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		// AES支持三种长度的密钥：128位、192位、256位。
		// 代码中这种就是128位的加密密钥，16字节 * 8位/字节 = 128位。
		// Security.addProvider(new BouncyCastleProvider());

		// removeCryptographyRestrictions();
		System.out.println("--------AES_CBC加密解密---------");
		// 42022219950504143X--81e9b8d77b194165b985f86eb6445f13

		String cbcResult = encryptCBC("42022219950504143X", "81e9b8d77b194165b985f86eb6445f13");
		System.out.println("aes_cbc加密结果:" + cbcResult);
		System.out.println();

		System.out.println("---------解密CBC---------");
		String cbcDecrypt = decryptCBC(cbcResult, "81e9b8d77b194165b985f86eb6445f13");
		System.out.println("aes解密结果:" + cbcDecrypt);
		System.out.println();

	}
}
